import pandas as pd
import numpy as np
import joblib
from xgboost import XGBClassifier
from tensorflow import keras

from pathlib import Path
from core.config import config, BASE_DIR

class DataProcessor:
    # 初始化
    def __init__(self):
        # 获取当前文件所在目录
        base_model_dir = BASE_DIR.joinpath(config.predmodel.base_model_dir).resolve()
        pred_model_dir = BASE_DIR.joinpath(config.predmodel.pred_model_dir).resolve()

        # 加载模型
        self.ordinal_encoder = joblib.load(base_model_dir.joinpath("ordinal_encoder.joblib").resolve())
        self.scaler_model = joblib.load(base_model_dir.joinpath("minmax_scaler.joblib").resolve())
        self.gbdt_model = joblib.load(base_model_dir.joinpath("gbdt_model.joblib").resolve())
        self.scaler_30 = joblib.load(base_model_dir.joinpath("minmax_scaler_30.joblib").resolve())
        # self.model = joblib.load(model_dir.joinpath("xgb_model.joblib").resolve())
        self.model = keras.models.load_model(pred_model_dir.joinpath("mlp_model.h5").resolve())

        # 需要编码的属性列
        self.columns_to_encode = ['SCMA_OID_PROD_TYPE', 'BEN_HEAD', 'SCMA_OID_BEN_TYPE', 'DIAG_CODE', 'CODES',
                                  'SCMA_OID_CL_STATUS', 'SCMA_OID_CL_TYPE', 'SCMA_OID_COUNTRY_TREATMENT', 'RJ_CODE_LIST',
                                  'POCY_PLAN_DESC', 'BEN_TYPE', 'PROV_LEVEL', 'MBR_TYPE', 'MEMBER_EVENT',
                                  'INSUR_INVOICE_IND', 'BEN_HEAD_TYPE']
        # 选择的新特征
        self.selected_new_features = ['New_Feature_0', 'New_Feature_1', 'New_Feature_2', 'New_Feature_3',
                                        'New_Feature_4', 'New_Feature_5', 'New_Feature_6', 'New_Feature_7',
                                        'New_Feature_8', 'New_Feature_9', 'New_Feature_10', 'New_Feature_11',
                                        'New_Feature_12', 'New_Feature_23', 'New_Feature_25']
        # 选择的原有特征
        self.chosen_attribute = ['RJ_CODE_LIST','DED_AMT','CODES','BEN_HEAD_TYPE','CL_THIRD_PARTY_PAY_AMT',
                                'CL_OWNER_PAY_AMT','DIAG_CODE','PROV_CODE','COPAY_PCT','SCMA_OID_CL_STATUS']
    
    def Encoder(self, data):
        """
        数据预处理的编码
        :Param data: 缺失值处理后的数据
        :return ordinal_encoder_data: 编码后的数据
        """    
        ordinal_encoder_data = data
        # 编码序列化后的数据集合。
        ordinal_encoder_data[self.columns_to_encode] = ordinal_encoder_data[self.columns_to_encode].astype(str)
        # 对特定列进行编码。
        ordinal_encoder_data[self.columns_to_encode] = self.ordinal_encoder.transform(
            ordinal_encoder_data[self.columns_to_encode])

        return ordinal_encoder_data

    def mis_value(self, data):
        """
        数据预处理的缺失值处理
        :Param data: 上传的后读取的pandas数据
        :return mis_data: 有缺失值的数据
        :return data: 无缺失值的数据
        """        
        # 先保存好需要打回补充的数据。
        mis_data = data[data[self.chosen_attribute].isna().any(axis=1)]
        data = data.dropna(subset=self.chosen_attribute)
        return mis_data, data

    def Scaler(self, data):
        """
        数据预处理的归一化
        :Param data: 经过初步处理的数据
        :return scaler_data: 归一化后的数据
        """        
        scaler_data = self.scaler_model.transform(data)
        return scaler_data

    def create_attribute(self, data):
        """
        数据预处理的新特征生成
        :Param data: 经过归一化后的数据
        :return X_test_with_new_features: 原始特征和新特征合并后的数据
        """
        # 生成测试集新特征
        test_with_new_features = self.gbdt_model.apply(data)  # 输出形状为 (n_samples, n_trees)
        test_with_new_features = test_with_new_features.reshape(-1, test_with_new_features.shape[1])

        # 新特征标准化(需使用训练集的scaler)
        test_with_new_features_standarized = self.scaler_30.transform(test_with_new_features)

        # 准备合并
        after_scale_X_test_neww = data

        # 构建测试集特征DataFrame
        test_new_df = pd.DataFrame(test_with_new_features_standarized,
                                   columns=[f'New_Feature_{i}' for i in range(test_with_new_features.shape[1])],
                                   index=after_scale_X_test_neww.index)
        test_new_df = test_new_df[self.selected_new_features]

        # 合并原始特征和新特征
        X_test_with_new_features = pd.concat([test_new_df, after_scale_X_test_neww], axis=1)
        return X_test_with_new_features
    
    def pre_manage(self, input_data):
        """
        数据预处理
        :Param input_data: 上传的后读取的pandas数据
        :return X_test: 特征
        :return label: 标签
        :return mis_data: 缺失值
        """
        # 缺失值标记，处理
        mis_data, unmis_data = self.mis_value(input_data)
        mis_data = pd.DataFrame(mis_data, columns=input_data.columns)
        unmis_data = pd.DataFrame(unmis_data, columns=input_data.columns)
        # 编码
        unmis_data = self.Encoder(unmis_data)
        # 拒绝理赔的设置为“1”，接受理赔的设置为“0”，其它情况都设置为2。
        label = unmis_data['CL_LINE_STATUS']
        label.replace('AC', 0, inplace=True)
        label.replace('RJ', 1, inplace=True)
        label.replace('PD', 2, inplace=True)
        label.replace('PV', 2, inplace=True)
        label.replace('RV', 2, inplace=True)
        label.replace('SU', 2, inplace=True)
        label.replace('IC', 2, inplace=True)
        label.replace('UE', 2, inplace=True)
        label.replace('RA', 2, inplace=True)
        label.replace('RC', 2, inplace=True)
        # id重排序
        label = pd.DataFrame(label, columns=['CL_LINE_STATUS'])
        label = label.reset_index()
        label = pd.DataFrame(label, columns=['CL_LINE_STATUS'])
        # 特征提取
        first_filtrate_data = unmis_data[self.chosen_attribute]
        # 归一化
        scaler_data = self.Scaler(first_filtrate_data)
        scaler_data = pd.DataFrame(scaler_data, columns=first_filtrate_data.columns)
        # 创建新特征
        X_test = self.create_attribute(scaler_data)
        # 返回新特征， 标签， 有缺失数据
        return X_test, label, mis_data

    def predict(self, X_test):
        """
        模型预测，对不同的模型有不同的结果
        :Param X_test: 预处理完成的输入特征
        :return: 对于深度学习模型，输出结果为概率，需要自己手动再分；对于机器学习模型结果为二分类，不需要再分
        """
        return self.model.predict(X_test)
    
    def result_classifier(self, pre_result):
        """
        将预测结果按区间分类：
        0-0.3 → 0
        0.3-0.6 → 1
        0.6-1.0 → 2
        :Param pre_result: 预测后的结果
        :return: 分类后的结果
        """
        # 将结果限制在[0,1]范围内（处理可能的越界值，从激活函数看理论上不会有）
        pre_result = np.clip(pre_result, 0, 1)
        
        # 使用向量化操作进行分类
        return np.select(
            condlist=[
                pre_result <= 0.3,
                pre_result <= 0.6,
                pre_result <= 1.0
            ],
            choicelist=[0, 1, 2],
            default=0  # 默认情况（理论上不会触发）
        )
    